import random
import math
import copy
import matplotlib.pyplot as plt

from VRP_SA.parameter import *

class Gene:
    def __init__(self, name='Gene', data=None):
        self.name = name
        self.length = customer_num + car_num
        if data is None:
            self.data = self._generate()
        else:
            # assert(self.length == len(data))
            self.data = data
        if self.isFeasible():
            self.fit = self.getFit() #适应度
        else:
            self.fit = 0
        self.chooseProb = 0  # 选择概率

    def readData(self):
        return copy.deepcopy(self.data)
    
    # 生成一个染色体，实数编码+二进制编码的二维数组
    # return a random gene with proper center assigned
    def _generate(self):
        data_customer = [i for i in range(1, customer_num)]
        random.shuffle(data_customer)

        # 从数列的中间，选车数-1个位置，插入Center(0)
        if car_num > 1:
            index = sorted(random.sample(range(1, len(data_customer)-1), car_num - 1))
            data_new = []
            for i in range(len(index)+1):
                if i == 0:
                    data_new += data_customer[: index[i]] + [CENTER]
                elif i == len(index):
                    data_new += data_customer[index[i-1]: ]
                else:
                    data_new += data_customer[index[i-1]: index[i]] + [CENTER]
            data_customer = data_new

        data_customer.insert(0, CENTER)  # 首位插入0：从配送中心出发，回到配送中心
        data_customer.append(CENTER)

        # 是否启用外包公司
        data_bid = [int(random.random()*1.1) for _ in range(bid_num)]

        data = [data_customer, data_bid]
        return data

    # 判断当前染色体的外包公司序列是否可行，即是否出现两个公司服务一个顾客的情况
    def isFeasible(self):
        data_bid = self.data[1]
        for i in range(customer_num):
            sum = 0
            for firm_no, serve in enumerate(data_bid):
                if serve == 1 and i in B[firm_no]: # 启用firm_no且顾客i可被firm_no服务
                    sum += 1
            if sum > 1: # 对任意一个顾客，如果出现重叠的情况就是不可行的
                return False
        return True

    # 计算适应度
    def getFit(self):
        data_customer = copy.deepcopy(self.data[0])
        data_bid = self.data[1]

        # 移除被外包的顾客
        for firm_no, serve in enumerate(data_bid):
            if serve == 1:
                for customer_no in B[firm_no]:
                    data_customer.remove(customer_no)

        fit = distCost = timeCost = overloadCost = bidCost = 0
        dist = []  # from this to next 从这一点到下一点的距离

        # calculate distance
        i = 1
        while i < len(data_customer):
            calculateDist = lambda x1, y1, x2, y2: math.sqrt(((x1 - x2) ** 2) + ((y1 - y2) ** 2)) # 匿名函数
            dist.append(calculateDist(xcoord[data_customer[i]], ycoord[data_customer[i]], xcoord[data_customer[i - 1]], ycoord[data_customer[i - 1]]))
            i += 1

        # distance cost
        distCost = sum(dist) * costPerKilo

        # time cost
        timeSpent = 0
        for i, pos in enumerate(data_customer):
            # skip first center
            if i == 0:
                continue
            # new car
            elif pos == CENTER:
                timeSpent = 0

            # update time spent on road
            timeSpent += (dist[i - 1])
            # arrive late
            if timeSpent > l[pos]:
                timeCost += HUGE
            else:
                timeSpent += (timeSpent < e[pos]) * (e[pos] - timeSpent) + s[pos] # 早到的等待时间+服务时间

        # overload cost
        load = 0
        for i, pos in enumerate(data_customer):
            # skip first center
            if i == 0:
                continue
            # at center, new car
            elif pos == CENTER:
                load = 0
            # normal
            else:
                load += d[pos] # demand
                # update load cost
                overloadCost += (HUGE * (load > capacity))

        # 3PL cost
        for firm_no, serve in enumerate(data_bid):
            if serve == 1:
                bidCost += p[firm_no]

        fit = distCost + timeCost + overloadCost + bidCost
        return 1 / fit

    def updateChooseProb(self, sumFit):
        self.chooseProb = self.fit / sumFit

    # plot this gene in a new window
    def plot(self):
        data_customer = copy.deepcopy(self.data[0])
        data_bid = self.data[1]

        # 移除被外包的顾客
        for firm_no, serve in enumerate(data_bid):
            if serve == 1:
                for customer_no in B[firm_no]:
                    data_customer.remove(customer_no)
        print('customer order:', data_customer)
        print('3PL:', data_bid)

        zeros = []
        for i in range(len(data_customer)):
            if data_customer[i] == 0:
                zeros.append(i)
        for i in range(len(zeros)-1):
            Xpath = [xcoord[j] for j in data_customer[zeros[i]:zeros[i+1]+1]]
            Ypath = [ycoord[j] for j in data_customer[zeros[i]:zeros[i+1]+1]]
            plt.plot(Xpath, Ypath, ls='-', c=color[i], zorder=1) #zorder大的图像在上层，zorder小的图像在下层（有可能被遮盖）

        Xcus = [xcoord[j] for j in data_customer]
        Ycus = [ycoord[j] for j in data_customer]
        plt.scatter(Xcus, Ycus, zorder=2)
        k = 0
        for firm_no, serve in enumerate(data_bid):
            if serve == 1:
                # print(B[firm_no])
                Xbid = [xcoord[j] for j in B[firm_no]]
                Ybid = [ycoord[j] for j in B[firm_no]]
                plt.scatter(Xbid, Ybid, c=color[k], zorder=2)
            k += 1

        for i in range(len(xcoord)):
            plt.text(xcoord[i], ycoord[i], str(i), zorder=3)
        plt.scatter([xcoord[0]], [ycoord[0]], marker='o', zorder=3) # CENTER
        plt.title(filename)
        plt.show()
